﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO.Ports;
using System.Diagnostics;
using System.Speech.Synthesis;
using System.Threading;

namespace WPFVDRUtility
{
    /// <summary>
    /// Interaction logic for VDRUtil.xaml
    /// </summary>
    public partial class VDRUtil : Window
    {
        // Summary:
        //     Specifies where the terminal string is from
        //     object.
        public enum TerminalTextSource
        {
            // Summary:
            //     Received from the user input
            //     
            UserInput = 0,
            //
            // Summary:
            //     Received from the COM Port
            ReceivedInput = 1,
        }

        // Set up the Speech
        SpeechSynthesizer _speechSynthesizer = new SpeechSynthesizer();

        // Set up the serial port
        static SerialPort _serialPort = null;

        // Set up the event handler for the COM Ports event
        private event RoutedEventHandler _comPorts = null;
        private event RoutedEventHandler _enableTerminal = null;

        // Define the delegate and then declare the delegate
        private delegate void TerminalUpdateDelegate(string str);
        private delegate void TerminalUpdateHexDelegate(string str, TerminalTextSource source);
        TerminalUpdateDelegate _updateTerminal = null;
        TerminalUpdateHexDelegate _updateTerminalHex = null;

        // Set up the event handler for the COM Port Settings
        private event SelectionChangedEventHandler _comSettings = null;

        private Thread readThread = null;
        static bool _continue;
        static int _iplus_plus_plus = 0;

        /// <summary>
        /// Resource Dictionaries
        /// </summary>
        ResourceDictionary _bubbleCream;
        ResourceDictionary _bureauBlack;
        ResourceDictionary _bureauBlue;
        ResourceDictionary _expression;

        public VDRUtil()
        {
            InitializeComponent();
        }

        private void InitEvents(Object sender, RoutedEventArgs e)
        {
            _serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialDataReceiveTerminal);

            // Register event for any setting change  
            _comSettings += new SelectionChangedEventHandler(ComSettingsChanged);
            this.comboBoxCom.SelectionChanged += new SelectionChangedEventHandler(ComSettingsChanged);
            this.ComBoBaud.SelectionChanged += new SelectionChangedEventHandler(ComSettingsChanged);
            this.ComBoBits.SelectionChanged += new SelectionChangedEventHandler(ComSettingsChanged);
            this.ComBoFlow.SelectionChanged += new SelectionChangedEventHandler(ComSettingsChanged);
            this.ComBoParity.SelectionChanged += new SelectionChangedEventHandler(ComSettingsChanged);
            this.ComBoStopBit.SelectionChanged += new SelectionChangedEventHandler(ComSettingsChanged);

            // Register event for the message for terminal
            _updateTerminal += UpdateTerminal;
            _updateTerminalHex += UpdateTerminalHex;
            _enableTerminal += new RoutedEventHandler(CheckTerminalEnable);

            // Check the terminal status
            _enableTerminal(sender, e);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // _speechSynthesizer.Rate = 1;
            // _speechSynthesizer.SpeakAsync("The time is " + DateTime.Now.ToShortTimeString());

            // Init the serial port
            // Create a new SerialPort object with default settings.
            _serialPort = new SerialPort();

            // List out the COM Port
            ListComPorts(this, new RoutedEventArgs());

            // Initial events
            InitEvents(sender, e);

            // Start the read thread
            //_continue = true;
            //readThread = new Thread(ReadTerminal);           
            //readThread.Start();

            // progress bar
            MakeIndeterminate(sender, e);


            ResourceDictionary resource = Application.Current.Resources;
            System.Collections.ObjectModel.Collection<ResourceDictionary> list = resource.MergedDictionaries;
            foreach (ResourceDictionary res in list)
            {
                Debug.WriteLine(res.Source);
            }

            // Theme
            Uri uri = new Uri(@".\Themes\XAML\BubbleCreme.xaml", UriKind.Relative);
            _bubbleCream = Application.LoadComponent(uri) as ResourceDictionary;

            uri = new Uri(@".\Themes\XAML\BureauBlack.xaml", UriKind.Relative);
            _bureauBlack = Application.LoadComponent(uri) as ResourceDictionary;

            uri = new Uri(@".\Themes\XAML\BureauBlue.xaml", UriKind.Relative);
            _bureauBlue = Application.LoadComponent(uri) as ResourceDictionary;

            uri = new Uri(@".\Themes\XAML\ExpressionDark.xaml", UriKind.Relative);
            _expression = Application.LoadComponent(uri) as ResourceDictionary;

            List<string> themes = new List<string>();
            themes.Add("Cream");
            themes.Add("Bureau Black");
            themes.Add("Bureau Blue");
            themes.Add("Expression");
            //themes.Add("Shiny Blue");
            //themes.Add("Shiny Real");
            //themes.Add("Twilight");
            //themes.Add("Whistle");

            // Add the string
            foreach(string str in themes) ResourceListBox.Items.Add(str);

            // User the default theme
            Application.Current.Resources.MergedDictionaries.Add(_bubbleCream);
        }

        private void MakeIndeterminate(object sender, RoutedEventArgs e)
        {        
            // statusBar.Items.Clear();
            ProgressBar progbar = new ProgressBar();
            progbar.Background = Brushes.Gray;
            progbar.Foreground = Brushes.Red;
            progbar.Width = 600;
            progbar.Height = 15;
            progbar.IsIndeterminate = true;
            statusBar.Items[1] = progbar;
            // statusBar.Items.Add(progbar);
        }

        /// <summary>
        /// This Event Handler is used to fill out the available COM Ports
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ListComPorts(Object sender, RoutedEventArgs e)
        {
            comboBoxCom.BeginInit();
            comboBoxCom.Items.Clear();
            foreach (string s in SerialPort.GetPortNames())
            {
                // try to close the existing port
                if (_serialPort.IsOpen)
                {
                    try
                    {
                        _serialPort.Close();
                    }
                    catch (Exception exClose) {
                        Debug.WriteLine(exClose.Message);
                    }
                }                                

                // try to assign the value and open the port
                // if the port can be opened, then it can be added to the combobox
                // otherwise, continue
                _serialPort.PortName = s;
                try
                {
                    // _serialPort.Open();               
                    comboBoxCom.Items.Add(s);
                    // _serialPort.Close();
                }
                catch (Exception exOpen)
                {
                    Debug.WriteLine(exOpen.Message);
                }
            }
            comboBoxCom.EndInit();          
            comboBoxCom.SelectedIndex = 0;

            UpdateComSettings(this);
        }

        /// <summary>
        /// This Event Handler is called when the Windows is closed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Closed(object sender, EventArgs e)
        {
            // Close the serial port
            if (_serialPort.IsOpen)
            {
                try
                {
                    _serialPort.Close();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
            }
            _continue = false;
            this.readThread.Join();
        }

        /// <summary>
        /// This API is designed to verify the basic fucntionality before it is integrated 
        /// into individual feature.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonTest_Click(object sender, RoutedEventArgs e)
        {

            try
            {
                
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            finally
            {

            }
        }

        private bool UpdateComSettings(Object sender)
        {
            try
            {
                // change the port setting
                if (_serialPort.IsOpen) _serialPort.Close();

                _serialPort.PortName = (string)comboBoxCom.SelectedItem;

                ComboBoxItem item = (ComboBoxItem)ComBoBaud.SelectedItem;
                _serialPort.BaudRate = int.Parse(item.Content.ToString());

                item = (ComboBoxItem)ComBoParity.SelectedItem;
                _serialPort.Parity = (Parity)Enum.Parse(typeof(Parity), item.Content.ToString());
                _serialPort.DataBits = int.Parse(ComBoBits.Text);
                
                // assign the stop bit
                string stopbit = ((ComboBoxItem)ComBoStopBit.SelectedItem).Content.ToString();
                if (stopbit.Equals("1")) _serialPort.StopBits = StopBits.One;
                else if (stopbit.Equals("1.5")) _serialPort.StopBits = StopBits.OnePointFive;
                else if (stopbit.Equals("2")) _serialPort.StopBits = StopBits.Two;
                
                // assign the handshake
                string handshake = ((ComboBoxItem)ComBoFlow.SelectedItem).Content.ToString();
                if(handshake.Equals("None")) _serialPort.Handshake = Handshake.None;
                else if(handshake.Equals("Hardware")) _serialPort.Handshake = Handshake.RequestToSend;
                else if(handshake.Equals("Xon/Xoff")) _serialPort.Handshake = Handshake.XOnXOff;
            
                // reopen the port with the new setting
                _serialPort.Open();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
                return false;
            }
            return true;
        }

        private void ComSettingsChanged(object sender, SelectionChangedEventArgs e)
        {
            UpdateComSettings(sender);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Hide or Show the Hex editor
            if (this.TextBoxTerminalHex.IsVisible)
                this.TextBoxTerminalHex.Visibility = Visibility.Hidden;
            else
                this.TextBoxTerminalHex.Visibility = Visibility.Visible;
        }


        private void SerialDataReceiveTerminal(Object sender, SerialDataReceivedEventArgs e)
        {
            if (_serialPort.IsOpen)
            {
                string str = _serialPort.ReadExisting();
                if (!String.IsNullOrEmpty(str))
                {
                    Debug.Write(str);
                    // Call the delegate in a different thread by calling either Invoke or BeginInvoke
                    Application.Current.Dispatcher.BeginInvoke(_updateTerminal, str);
                }
            }
        }

        private void SerialDataErrorTerminal(Object sender, SerialErrorReceivedEventArgs e)
        {

        }

        /// <summary>
        /// This API will read the information from the COM port
        /// and fill in the text control
        /// </summary>
        public void ReadTerminal()
        {
            while (_continue)
            {
                try
                {
                    if (_serialPort.IsOpen)
                    {
                        string str = _serialPort.ReadExisting();
                        if (!String.IsNullOrEmpty(str))
                        {
                            Debug.Write(str);
                            
                            // Application.Current.Dispatcher.Invoke(
                            Application.Current.Dispatcher.BeginInvoke(
                                _updateTerminal,
                                str);
                        }
                    }
                }
                catch (Exception e)
                {
                    // Debug.WriteLine(e.Message);
                }
                Thread.Sleep(10);
            }
        }

        /// <summary>
        /// This API is used to update the Hex Terminal
        /// </summary>
        /// <param name="str"></param>
        private void UpdateTerminalHex(string str, TerminalTextSource source)
        {
            string hex = "";
            foreach (char c in str)
            {
                int tmp = c;
                hex += String.Format(" {0:X2}", (uint)System.Convert.ToUInt32(tmp.ToString()));

                // If it is a different line, then put it as a different line.
                if (c == '\r')
                {
                    hex += '\n';
                }
            }
          
            // The code will change all the text. We need a right solution
            // If it is the user input, change the text color to blue
            if (source == TerminalTextSource.UserInput)
                TextBoxTerminalHex.Foreground = Brushes.Blue;
            else
                TextBoxTerminalHex.Foreground = Brushes.Black;

            TextBoxTerminalHex.AppendText(hex);
            TextBoxTerminalHex.ScrollToEnd();
        }

        /// <summary>
        /// This API is used to update both Terminal and Hex Terminal
        /// </summary>
        /// <param name="str"></param>
        private void UpdateTerminal(string str)
        {
            // no processing is required if the string is empty or null
            if (String.IsNullOrEmpty(str)) return;

            TextBoxterminal.AppendText(str);

            TextBoxterminal.ScrollToEnd();

            // This is a good code change. It always select the last character
            TextBoxterminal.Select(TextBoxterminal.Text.Length, 1);

            UpdateTerminalHex(str, TerminalTextSource.ReceivedInput);
        }


        /// <summary>
        /// This API is used to get the input command from the user
        /// Then it should call the event handler to send the command over
        /// and ready to pick up the receive code
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TextBoxterminal_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.OemPlus)
            {
                _iplus_plus_plus++;
                if (_iplus_plus_plus == 3)
                {
                    // Send "+++" command
                    _serialPort.Write("+++");

                    // Update the hex value
                    _updateTerminalHex("+++", TerminalTextSource.UserInput);

                    // reset the value
                    _iplus_plus_plus = 0;
                    return;
                }
            }
            else // reset it when it is not '+'
                _iplus_plus_plus = 0;

            if (e.Key == Key.Enter)
            {
                // the user presses the enter key, it is time to get the line content
                // and send over to the com port for processing
                string str = this.TextBoxterminal.GetLineText(this.TextBoxterminal.LineCount - 2);

                // split the input
                char[] chars = new char[2];
                chars[0] = '$';
                chars[1] = '>';
                string[] codes = str.Split(chars);

                // get the command line 
                string command = codes[codes.Length - 1];
                command.Trim();

                // send the command to com port
                if (_serialPort.IsOpen) 
                {
                    _serialPort.Write(command);
                    _updateTerminalHex(command, TerminalTextSource.UserInput);
                }
            }               
        }

        private void TextBoxterminal_TextChanged(object sender, TextChangedEventArgs e)
        {
          
        }
        /// <summary>
        /// Exit the application
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnExit(object sender, RoutedEventArgs e)
        {
            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAbout(object sender, RoutedEventArgs e)
        {

        }

        private void CheckTerminalEnable(object sender, RoutedEventArgs e)
        {
            if (_serialPort.IsOpen) TextBoxterminal.IsEnabled = true;
            else TextBoxterminal.IsEnabled = false;
        }

        private void CloseComPort_Click(object sender, RoutedEventArgs e)
        {
            if (_serialPort.IsOpen)
            {
                try
                {
                    _serialPort.Close();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);                    
                }
            }
            _enableTerminal(this, e);
        }

        private void OpenComPort_Click(object sender, RoutedEventArgs e)
        {
            if (!_serialPort.IsOpen)
            {
                try
                {
                    _serialPort.Open();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "FRC VDR Utility", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
            }
            _enableTerminal(this, e);
        }

        private void buttonExit_Click(object sender, RoutedEventArgs e)
        {
            Application.Current.Shutdown();
        }

        private void ResourceTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            try
            {
                string text = this.ResourceTextBox.Text;
                Application.Current.Resources.MergedDictionaries.Clear();
                if (text.Equals("Cream")) Application.Current.Resources.MergedDictionaries.Add(_bubbleCream);
                else if (text.Equals("Bureau Black")) Application.Current.Resources.MergedDictionaries.Add(_bureauBlack);
                else if (text.Equals("Bureau Blue")) Application.Current.Resources.MergedDictionaries.Add(_bureauBlue);
                else if (text.Equals("Expression")) Application.Current.Resources.MergedDictionaries.Add(_expression);

            }
            catch (Exception)
            {                
                throw;
            }
        }
    }
}
